# Copyright 2020 The Pigweed Authors
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
# use this file except in compliance with the License. You may obtain a copy of
# the License at
#
#     https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations under
# the License.

load("@rules_cc//cc:cc_library.bzl", "cc_library")
load("@rules_python//sphinxdocs:sphinx_docs_library.bzl", "sphinx_docs_library")
load("//pw_build:compatibility.bzl", "incompatible_with_mcu")
load("//pw_build:pw_facade.bzl", "pw_facade")
load("//pw_unit_test:pw_cc_test.bzl", "pw_cc_test")

package(
    default_visibility = ["//visibility:public"],
    features = [
        "-layering_check",
        "-ctad_warnings",
    ],
)

licenses(["notice"])

constraint_setting(
    name = "backend_constraint_setting",
)

# TODO: pwbug.dev/328679085 - Remove this alias once no-one uses it.
alias(
    name = "facade",
    actual = "pw_malloc.facade",
)

cc_library(
    name = "common",
    linkopts = [
        # Link options that replace dynamic memory operations in standard
        # library with the pigweed malloc.
        "-Wl,--wrap=malloc",
        "-Wl,--wrap=free",
        "-Wl,--wrap=realloc",
        "-Wl,--wrap=calloc",
        "-Wl,--wrap=_malloc_r",
        "-Wl,--wrap=_realloc_r",
        "-Wl,--wrap=_free_r",
        "-Wl,--wrap=_calloc_r",
    ],
    visibility = ["//visibility:private"],
    deps = [
        "//pw_allocator:allocator",
        "//pw_allocator:synchronized_allocator",
        "//pw_allocator:tracking_allocator",
        "//pw_bytes",
        "//pw_numeric:checked_arithmetic",
    ],
)

pw_facade(
    name = "pw_malloc",
    srcs = ["malloc.cc"],
    hdrs = [
        "public/pw_malloc/config.h",
        "public/pw_malloc/malloc.h",
    ],
    backend = ":backend",
    strip_include_prefix = "public",
    deps = [
        ":common",
        ":config_override",
        "//pw_assert:check",
    ],
    alwayslink = 1,
)

label_flag(
    name = "config_override",
    build_setting_default = "//pw_build:default_module_config",
)

label_flag(
    name = "backend",
    build_setting_default = ":backend_multiplexer",
)

constraint_value(
    name = "best_fit_backend",
    constraint_setting = "//pw_malloc:backend_constraint_setting",
)

# TODO(b/376730645): Remove deprecated backends.
constraint_value(
    name = "best_fit_block_allocator_backend",
    constraint_setting = "//pw_malloc:backend_constraint_setting",
)

constraint_value(
    name = "bucket_allocator_backend",
    constraint_setting = "//pw_malloc:backend_constraint_setting",
)

# TODO(b/376730645): Remove deprecated backends.
constraint_value(
    name = "bucket_block_allocator_backend",
    constraint_setting = "//pw_malloc:backend_constraint_setting",
)

constraint_value(
    name = "first_fit_backend",
    constraint_setting = "//pw_malloc:backend_constraint_setting",
)

# TODO(b/376730645): Remove deprecated backends.
constraint_value(
    name = "dual_first_fit_block_allocator_backend",
    constraint_setting = "//pw_malloc:backend_constraint_setting",
)

# TODO(b/376730645): Remove deprecated backends.
constraint_value(
    name = "first_fit_block_allocator_backend",
    constraint_setting = "//pw_malloc:backend_constraint_setting",
)

# TODO(b/376730645): Remove deprecated backends.
constraint_value(
    name = "last_fit_block_allocator_backend",
    constraint_setting = "//pw_malloc:backend_constraint_setting",
)

constraint_value(
    name = "freelist_backend",
    constraint_setting = "//pw_malloc:backend_constraint_setting",
)

constraint_value(
    name = "worst_fit_backend",
    constraint_setting = "//pw_malloc:backend_constraint_setting",
)

# TODO(b/376730645): Remove deprecated backends.
constraint_value(
    name = "worst_fit_block_allocator_backend",
    constraint_setting = "//pw_malloc:backend_constraint_setting",
)

# TODO(b/376730645): Remove deprecated backends.
alias(
    name = "backend_multiplexer",
    actual = select({
        "//pw_malloc:best_fit_block_allocator_backend": "//pw_malloc:best_fit",
        "//pw_malloc:bucket_allocator_backend": "//pw_malloc:bucket_allocator",
        "//pw_malloc:bucket_block_allocator_backend": "//pw_malloc:bucket_allocator",
        "//pw_malloc:dual_first_fit_block_allocator_backend": "//pw_malloc:first_fit",
        "//pw_malloc:first_fit_block_allocator_backend": "//pw_malloc:first_fit",
        "//pw_malloc:last_fit_block_allocator_backend": "//pw_malloc:first_fit",
        "//pw_malloc:worst_fit_block_allocator_backend": "//pw_malloc:worst_fit",
        "//pw_malloc_freelist:backend": "//pw_malloc:bucket_allocator",
        "//pw_malloc_freertos:backend": "//pw_malloc_freertos",
        "//conditions:default": "//pw_build:unspecified_backend",
    }),
    visibility = ["//targets:__pkg__"],
)

cc_library(
    name = "best_fit",
    srcs = ["best_fit.cc"],
    features = ["-conversion_warnings"],
    deps = [
        ":pw_malloc.facade",
        "//pw_allocator:best_fit",
    ],
)

# TODO(b/376730645): Remove deprecated backends.
cc_library(
    name = "best_fit_block_allocator",
    features = ["-conversion_warnings"],
    deps = [":best_fit"],
)

cc_library(
    name = "bucket_allocator",
    srcs = ["bucket_allocator.cc"],
    features = ["-conversion_warnings"],
    deps = [
        "//pw_allocator:bucket_allocator",
        "//pw_malloc:facade",
    ],
)

# TODO(b/376730645): Remove deprecated backends.
cc_library(
    name = "bucket_block_allocator",
    features = ["-conversion_warnings"],
    deps = [":bucket_allocator"],
)

cc_library(
    name = "first_fit",
    srcs = ["first_fit.cc"],
    features = ["-conversion_warnings"],
    deps = [
        "//pw_allocator:first_fit",
        "//pw_malloc:facade",
    ],
)

# TODO(b/376730645): Remove deprecated backends.
cc_library(
    name = "dual_first_fit_block_allocator",
    features = ["-conversion_warnings"],
    deps = [":first_fit"],
)

# TODO(b/376730645): Remove deprecated backends.
cc_library(
    name = "first_fit_block_allocator",
    features = ["-conversion_warnings"],
    deps = [":first_fit"],
)

# TODO(b/376730645): Remove deprecated backends.
cc_library(
    name = "last_fit_block_allocator",
    features = ["-conversion_warnings"],
    deps = [":first_fit"],
)

cc_library(
    name = "worst_fit",
    srcs = ["worst_fit.cc"],
    features = ["-conversion_warnings"],
    deps = [
        "//pw_allocator:worst_fit",
        "//pw_malloc:facade",
    ],
)

cc_library(
    name = "testing",
    testonly = True,
    srcs = ["malloc.cc"],
    hdrs = [
        "public/pw_malloc/config.h",
        "public/pw_malloc/internal/testing.h",
        "public/pw_malloc/malloc.h",
    ],
    defines = [
        "PW_MALLOC_METRICS_INCLUDE=\\\"pw_malloc/internal/testing.h\\\"",
        "PW_MALLOC_METRICS_TYPE=::pw::malloc::internal::TestMetrics",
        "PW_MALLOC_BLOCK_OFFSET_TYPE=uint16_t",
        "PW_MALLOC_MIN_BUCKET_SIZE=64",
        "PW_MALLOC_NUM_BUCKETS=4",
    ],
    features = ["-conversion_warnings"],
    implementation_deps = [
        "//pw_assert:check",
        "//pw_tokenizer:decoder",
    ],
    strip_include_prefix = "public",

    # Only run the test if no backend is set to ensure there is no system
    # allocator.
    target_compatible_with = select({
                                 "//pw_malloc:best_fit_block_allocator_backend": ["@platforms//:incompatible"],
                                 "//pw_malloc:bucket_allocator_backend": ["@platforms//:incompatible"],
                                 "//pw_malloc:bucket_block_allocator_backend": ["@platforms//:incompatible"],
                                 "//pw_malloc:dual_first_fit_block_allocator_backend": ["@platforms//:incompatible"],
                                 "//pw_malloc:first_fit_block_allocator_backend": ["@platforms//:incompatible"],
                                 "//pw_malloc:last_fit_block_allocator_backend": ["@platforms//:incompatible"],
                                 "//pw_malloc:worst_fit_block_allocator_backend": ["@platforms//:incompatible"],
                                 "//pw_malloc_freelist:backend": ["@platforms//:incompatible"],
                                 "//pw_malloc_freertos:backend": ["@platforms//:incompatible"],
                                 "//conditions:default": [],
                             }) +

                             # Currently only supported for host unit tests on Linux.
                             select({
                                 "@platforms//os:linux": [],
                                 "//conditions:default": ["@platforms//:incompatible"],
                             }) +

                             # Only run using the light framework, since gtest allocates objects before
                             # the test fixture initializes the heap.
                             select({
                                 "//pw_unit_test:backend_is_light": [],
                                 "//conditions:default": ["@platforms//:incompatible"],
                             }) +

                             # Don't run with ASAN or TSAN, since they wrap malloc.
                             select({
                                 "//pw_toolchain/host_clang:asan_enabled": ["@platforms//:incompatible"],
                                 "//pw_toolchain/host_clang:tsan_enabled": ["@platforms//:incompatible"],
                                 "//conditions:default": [],
                             }),
    visibility = ["//visibility:private"],
    deps = [
        ":common",
        "//pw_tokenizer:decoder",
        "//pw_unit_test:light",
    ],
)

pw_cc_test(
    name = "best_fit_test",
    srcs = [
        "best_fit.cc",
        "malloc_test.cc",
    ],
    # TODO: b/379937425 - Enable when internal toolchains are sorted out.
    tags = ["manual"],
    deps = [
        ":testing",
        "//pw_allocator:best_fit",
    ],
)

pw_cc_test(
    name = "bucket_allocator_test",
    srcs = [
        "bucket_allocator.cc",
        "malloc_test.cc",
    ],
    # TODO: b/379937425 - Enable when internal toolchains are sorted out.
    tags = ["manual"],
    deps = [
        ":testing",
        "//pw_allocator:bucket_allocator",
    ],
)

pw_cc_test(
    name = "first_fit_test",
    srcs = [
        "first_fit.cc",
        "malloc_test.cc",
    ],
    # TODO: b/379937425 - Enable when internal toolchains are sorted out.
    tags = ["manual"],
    deps = [
        ":testing",
        "//pw_allocator:first_fit",
    ],
)

pw_cc_test(
    name = "worst_fit_test",
    srcs = [
        "malloc_test.cc",
        "worst_fit.cc",
    ],
    # TODO: b/379937425 - Enable when internal toolchains are sorted out.
    tags = ["manual"],
    deps = [
        ":testing",
        "//pw_allocator:worst_fit",
    ],
)

filegroup(
    name = "doxygen",
    srcs = [
        "public/pw_malloc/config.h",
        "public/pw_malloc/malloc.h",
    ],
)

sphinx_docs_library(
    name = "docs",
    srcs = [
        "backends.rst",
        "docs.rst",
    ],
    prefix = "pw_malloc/",
    target_compatible_with = incompatible_with_mcu(),
)
